#This tutorial uses the basic transistor block built in Ciranova Tutorial 2, MyTransistorUnit, to build a classic SRAM cell featuring cross-coupled transistors.  It assumes you have already worked through the other ciranova tutorials as well as the others on the wiki.  Comments are inline; you should include this code within the rest of the tutorials if you want to try it out.

# !!!!
# don't forget to add the line:
#    lib.definePcell(FiveT_FPGACell,     "FiveT_FPGACell")
# to your __init__.py if you want to be able to use this cell!
# !!!!

__version__ = "$Revision: #1 $"

from cni.dlo import *
from cni.geo import *
from cni.constants import *
from tutorial3 import *

class FiveT_FPGACell(DloGen):

    # defineParamSpecs/setupParams are pretty much copy/pasted straight from the tutorial #2
    @classmethod
    def defineParamSpecs(cls, specs):
        oxide = 'thin'
        width = specs.tech.getMosfetParams('nmos', oxide, 'minWidth')
        specs('width', width, 'device width', RangeConstraint(width, 10*width, USE_DEFAULT))
        specs('oxide', oxide, 'Oxide (thin or thick)', ChoiceConstraint(['thin', 'thick']))

    def setupParams(self, params):
        self.oxide = params['oxide']
        self.width = params['width']
        self.diffLayer = Layer('diff')
        self.gateLayer = Layer('poly1')
        self.metalLayer = Layer('metal1')
        self.gatecontactLayer = Layer('PCONT')
        
        self.width = params['width']
        self.default_tran = ParamArray()
        self.default_tran['oxide'] = self.oxide
        self.default_tran['width'] = self.width
        
    def genLayout(self):   
        # since the SRAM cell is rotationally symmetrical, we'll make identical right and left sides and then fit them together
        leftside = self.makeHalf('left')
        rightside = self.makeHalf('right')
        
        rightside.mirrorX()
        rightside.mirrorY()
        
        fgPlace(rightside, EAST, leftside)

        # smash left and rightsides together; the shapefilter will force DRC to only pay attention to the diffusion and poly layers
        # while filling in the holes between the poly
        newpoly = fgAbut(rightside, EAST, leftside, [self.gateLayer], filter=ShapeFilter([self.diffLayer,self.gateLayer])).getComp(0)
        
        right_floater = rightside.getComp(-1)
        left_floater = leftside.getComp(-1)
        
        left_pmos = leftside.getComp(-2)
        right_pmos = rightside.getComp(-2)
        
        # we're clipping the overlap from the big abut; trim the overlap on the opposite side, since the floater is on the opposite side
        # also fill in any holes
        #alignEdge(newpoly.getComp(0), EAST, right_floater, EAST)
        #alignEdge(newpoly.getComp(1), WEST, left_floater, WEST)
        
        # add the feedback connections (left_floater.getRect2() is the metal part; Rect1() is the poly)
        RoutePath.StraightLine(left_pmos.findInstPin('D'), left_floater.getRect2(), self.metalLayer) 
        RoutePath.StraightLine(right_pmos.findInstPin('D'), right_floater.getRect2(), self.metalLayer) 
        
        # all that remains is pretty easy:
        # the last shapes to do are add VSS, VDD, BL, and WL and square-off the central NWell
        # and, of course, pins are also needed. but, i'm tired right now so they'll have to wait!
               
    def makeHalf(self,side):
        # lump everything on a half into a group so that its (theoretically) easier to manipulate later
        half_group = Grouping(side)
        
        self.default_tran['tranType'] = 'nmos'
        
        nmos_wl = Instance('MyTransistorUnit', self.default_tran, ['D','G','S','B'], 'nmos_wl_' + side)
        nmos_q = Instance('MyTransistorUnit', self.default_tran, ['D','G','S','B'], 'nmos_q_' + side)
        half_group.add(nmos_q)
        half_group.add(nmos_wl)
        
        #  merge the drain and source of the two NMOS
        nmos_wl.mirrorX()
        pinBox = nmos_wl.findInstPin('D').getBBox()
        tranBox = nmos_wl.getBBox()
        ext1 = pinBox.bottom - tranBox.bottom
        ext2 = tranBox.top - pinBox.top
        overlap = pinBox.getHeight() + 2.0 * min(ext1, ext2)
        place(nmos_wl, NORTH, nmos_q, -overlap)
        
        # add a contact on the left side for the WL contact - have to do this AFTER the above merge
        con_box = nmos_wl.findInstPin('G').getBBox()
        wl_contact = DeviceContact(self.gateLayer, self.metalLayer, con_box, name='nmos_wl_gate_' +side)  
        stretchy = fgAbut(wl_contact, WEST, nmos_wl, [self.gateLayer])
        half_group.add(stretchy)
        half_group.add(wl_contact)
        
        # make the PMOS
        self.default_tran['tranType'] = 'pmos'
        pmos_q = Instance('MyTransistorUnit', self.default_tran, ['D','G','S','B'], 'pmos_q_' + side)
        half_group.add( fgAbut(pmos_q, EAST, nmos_q, [self.gateLayer]) )
        half_group.add(pmos_q)
        
        # add a "floating" contact to represent the gate of the opposite side's Q; stick it
        # above the PMOS to meet that contact-to-contact clearance requirement
        floating_contact = DeviceContact(self.gateLayer, self.metalLayer, con_box, name='floating_gate_'+side) 
        fgPlace(floating_contact, NORTH, pmos_q, filter=ShapeFilter([self.diffLayer,self.gateLayer]))
        
        half_group.add(floating_contact)
        
        # get rid of the superflous nodes
        fgMerge(half_group, self.gateLayer)
        
        return half_group